VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "ISprite_Ghost"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
'====================================================================================
'ISprite_Ghost - The monsters/ghosts that run around the maze
'====================================================================================
Option Explicit
Implements ISprite

'The ghost sprites are preloaded, this array here is set to reference those preloaded
'sprites in the 'Let GhostType' function.
Private Sprite(2) As ImageTGA

'The animation state of the ghost
Private Enum PM_GHOSTANIM
    GA_Move = 0     'Normal moving animation
    GA_Love = 1     'in-love animation when Pac Mar eats a heart power-pill
    GA_Eaten = 2    'When ghost has been eaten by Pac Mar and heads back to the pen
End Enum
Private CurrentAnim As PM_GHOSTANIM

'The type of ghost to use
Public Enum PM_GHOSTTYPE
    GT_Zim = 1      'Use Zim sprite
    GT_Dib = 2      'Use Dib sprite
    GT_Kroc = 3     'Use Kroc sprite
    GT_Gir = 4      'Use Gir sprite
End Enum
Private Ghost As PM_GHOSTTYPE

'The behaviour pattern of the ghost
Public Enum PM_GHOSTAI
    AI_Random = 0   'Ghost chooses random turns at junctions
    AI_Hunt = 1     'Ghost actively tries to move toward Mar on the whole level
    AI_Seek = 2     'Ghost tries to get ahead of Mar
    AI_Follow = 3   'Ghost tries to stay behind Mar
    AI_Love = 99    'Ghost is in edible mode and moves slowly away from Pac Mar
End Enum
Public AIType As PM_GHOSTAI

'Interface --------------------------------------------------------------------------
Public XPosition As Long          'Location of ghost on the level in _pixels_
Public YPosition As Long          'and the same vertically

Public Direction As PM_DIRECTION  'The Ghost's current direction
Public Pending As PM_DIRECTION    'Next direction to go at a junction
Public IsMoving As Boolean        'If the ghost is inbetween tiles
Public Speed As Long              'Pixels/Frame speed

'Tracking ---------------------------------------------------------------------------
Private IsInLove As Boolean       'If the ghost is in edible mode
Private IsEaten As Boolean        'If the ghost has been eaten
Private OldAI As PM_GHOSTAI       'Remember the AI in use before changing mode
Private OldSpeed As Long          'Remember the speed in use before changing mode

Private IsJumping As Boolean      'Makes the ghost hop to highlight their location
Private JumpHeight As Long
Private JumpVector As Long

'This block of properties implements the 'ISprite' interface to allow this class to
'unify with the Mar sprite class for moving around the maze.
Private Property Get ISPrite_Direction() As PM_DIRECTION: ISPrite_Direction = Direction: End Property
Private Property Let ISPrite_Direction(ByVal NewDirection As PM_DIRECTION): Direction = NewDirection: End Property
Private Property Get ISPrite_Pending() As PM_DIRECTION: ISPrite_Pending = Pending: End Property
Private Property Let ISPrite_Pending(ByVal NewDirection As PM_DIRECTION): Pending = NewDirection: End Property
Private Property Get ISprite_XPosition() As Long: ISprite_XPosition = XPosition: End Property
Private Property Let ISprite_XPosition(ByVal Value As Long): XPosition = Value: End Property
Private Property Get ISprite_YPosition() As Long: ISprite_YPosition = YPosition: End Property
Private Property Let ISprite_YPosition(ByVal Value As Long): YPosition = Value: End Property
Private Property Get ISprite_Speed() As Long: ISprite_Speed = Speed: End Property
Private Property Let ISprite_Speed(ByVal Value As Long): Speed = Value: End Property
Private Property Get ISprite_IsMoving() As Boolean: ISprite_IsMoving = IsMoving: End Property
Private Property Let ISprite_IsMoving(ByVal Value As Boolean): IsMoving = Value: End Property

'GET GhostType: Return the type of ghost to the caller
'====================================================================================
Public Property Get GhostType() As PM_GHOSTTYPE: GhostType = Ghost: End Property

'LET GhostType: Set the type of ghost this class is to be
'====================================================================================
Public Property Let GhostType(ByVal NewType As PM_GHOSTTYPE)
    Dim K As Long
    
    'Setting the ghost type initializes the graphics, starting location and defaults
    'There is no need to call this command twice
    
    'Defaults used regardless of type of ghost
    IsMoving = False        'Currently not in motion
    Pending = 0             'No direction for next turn
    IsEaten = False         'Hasn't been eaten by Pac Mar
    IsInLove = False        'Not in edible mode
    CurrentAnim = GA_Move   'Show in normal style
    
    Select Case NewType
        Case PM_GHOSTTYPE.GT_Kroc '--------------------------------------------------
            'Kroc tries to follow Mar
            AIType = AI_Follow
            Speed = DiffModes(Level.Data.Difficulty).KrocSpeed
            'Position in the level
            XPosition = (Level.Data.PenX * TileSize) + HalfTileSize
            YPosition = (Level.Data.PenY - 1) * TileSize
            Direction = DIR_LEFT
            IsMoving = True
            
        Case PM_GHOSTTYPE.GT_Dib '---------------------------------------------------
            'Dib seeks Mar out actively
            AIType = AI_Hunt
            Speed = DiffModes(Level.Data.Difficulty).DibSpeed
            'Position in the level
            XPosition = (Level.Data.PenX + 1) * TileSize   'Place Dib in the pen
            YPosition = (Level.Data.PenY) * TileSize
            Direction = DIR_LEFT
            
        Case PM_GHOSTTYPE.GT_Zim '---------------------------------------------------
            'Zim tries to always get ahead of Mar
            AIType = AI_Seek
            Speed = DiffModes(Level.Data.Difficulty).ZimSpeed
            'Position in the level
            XPosition = (Level.Data.PenX) * TileSize
            YPosition = (Level.Data.PenY) * TileSize
            Direction = DIR_RIGHT
            
        Case PM_GHOSTTYPE.GT_Gir '---------------------------------------------------
            'Gir moves entirely random
            AIType = AI_Random
            Speed = DiffModes(Level.Data.Difficulty).GirSpeed
            'Position in the level
            XPosition = (Level.Data.PenX * TileSize) + HalfTileSize
            YPosition = (Level.Data.PenY) * TileSize
            Direction = DIR_LEFT
            IsMoving = True
            
    End Select
    'Reference the already loaded sprites
    For K = 0 To 2
        Set Sprite(K) = Nothing
        Set Sprite(K) = Sprites.Ghosts(NewType, K)
    Next K
    
    'Remember this choice
    Ghost = NewType
End Property

'GET InLove: Return if the ghost is in love or not (edible)
'====================================================================================
Public Property Get InLove() As Boolean: InLove = IsInLove: End Property

'LET InLove: Switch on or off the "edible mode" for the ghost
'====================================================================================
Public Property Let InLove(ByVal IsEnabled As Boolean)
    'Make the ghost hop to highlight its location
    If IsEaten = False Then IsJumping = True
    
    'Don't allow IsInLove to be set to true when it's already true because the
    'remembered speed and AI will be overwritten and stop the ghost coming out of
    'edible mode
    If IsEnabled = IsInLove Then Exit Property
    
    IsInLove = IsEnabled    'True / False
    If IsInLove = True Then
        'Reverse direction
        Me.Direction = GetReverseDirection(Me.Direction): Pending = 0
        
        OldAI = AIType          'Remember old AI
        OldSpeed = Speed        'Remember old speed
        AIType = AI_Love        'Change ghost behaviour
        CurrentAnim = GA_Love   'Change animation
        Sprite(GA_Love).CurrentFrame = 0
        Speed = 1               'Slow down
    Else
        'Return the ghost back to normal mode
        AIType = OldAI          'Return to previous behaviour
        Speed = OldSpeed        'Return to previous speed
        CurrentAnim = GA_Move   'Return to normal animation
        Sprite(GA_Move).CurrentFrame = 0
        
    End If
End Property

'GET Eaten: Return if the ghost has been eaten
'====================================================================================
Public Property Get Eaten() As Boolean: Eaten = IsEaten: End Property

'LET Eaten: Switch on or off eaten mode for the ghost
'====================================================================================
Public Property Let Eaten(ByVal IsEnabled As Boolean)
    'Don't allow the same value to be set twice as this will mess things up.
    If IsEaten = IsEnabled Then Exit Property
    
    IsEaten = IsEnabled
    If IsEaten = True Then
        'Set the ghost to eaten mode and speed up
        CurrentAnim = GA_Eaten  'Change animation
        Sprite(GA_Eaten).CurrentFrame = 0
        Speed = 8               'Speed up
        Me.Pending = MakeDecision(GetTileFromPx(Me.XPosition), GetTileFromPx(Me.YPosition))
        
        'Play eaten sound
        SFX.EatGhost.Play False
        
        'Show a message
        If Status.IsBottomAnimationFinished = True Then
            Select Case Int(Rnd(1) * 4)
                Case 0: Status.SetMessageBottom "0wned!", SLIDEUP, 32, False
                Case 1: Status.SetMessageBottom "=^_^=", SLIDEUP, 32, False
                Case 2: Status.SetMessageBottom "Yummy", SLIDEDOWN, 32, False
                Case 3: Status.SetMessageBottom "n_n", SLIDEDOWN, 32, False
            End Select
        End If
        
        'Add to score
        Level.IncreaseScore (200 * (2 ^ Range(Level.GhostMultiplier, 0, 3)))
        
        'Add to the number of ghosts eaten during edible-mode
        Level.GhostMultiplier = Level.GhostMultiplier + 1
        Level.StatsGhostsEaten = Level.StatsGhostsEaten + 1
        Game.TotalGhosts = Game.TotalGhosts + 1
        
        'Add the score particle (up to +1600)
        If Level.GhostMultiplier <= 4 Then
            GameMedia.Particles.Create Level.GhostMultiplier, (XPosition + 32), (YPosition + 32), 64
        End If
        
        'If all four were eaten show a message
        If Level.GhostMultiplier = 4 Then
            Status.SetMessageBottom "*** ZOMG! All 4! ***", SLIDECENTER, 64, False
            'If all four ghosts have been eaten, cancel the edible mode. This is so
            'that when all 4 ghosts pen-in and come out normal, it's not possible
            'for edible mode to still be in effect, leading to the ability to eat
            'more than 4 ghosts in a row for insane points. Disable this line to
            'enable the ability to eat more than 4 ghosts in a row.
            Level.LoveTimeout = 1
        End If
    End If
End Property

'====================================================================================
'ISPRITE Animate: Animate and move the ghost around the maze
'====================================================================================
Public Sub Animate(): ISprite_Animate: End Sub
Private Sub ISprite_Animate()
    'Whilst the screen itself refreshes 24 times a second, only animate the frames
    'twelve times a second so that the sprites require less drawn frames
    If (Frame.Count Mod 3) = 0 Then Sprite(CurrentAnim).Animate
    
    'Only move if gameplay has started and the game is not paused
    If (Level.Paused = True) Then Exit Sub
    
    'If gameply is underway, move sprites etc
    If ScreenMode = SM_Play Then '---------------------------------------------------
        'Move the ghost using the generic sprite moving routine
        Level.MoveSprite Me
        
        'Get the nearest tile the sprite is within
        Dim Pos As PM_XY
        Pos.X = GetTileFromPx(Me.XPosition)
        Pos.Y = GetTileFromPx(Me.YPosition)
        
        'If the ghost has entered the pen after being eaten, return to normal
        If (Pos.X = Level.Data.PenX Or Pos.X = Level.Data.PenX + 1) And _
           (Pos.Y = Level.Data.PenY) And (Me.XPosition Mod TileSize) <= 16 And _
           (Me.YPosition Mod TileSize) <= 16 And (IsEaten = True) _
        Then
            IsEaten = False: Me.InLove = False
        End If
        
        'If the ghost is in edible mode, add a stream of love hearts behind him.
        'Only if more than 1 seconds remaining, this is so you can see when edible
        'mode is about to run out
        If (Me.InLove = True) And (IsEaten = False) Then
            If IsJumping Then
                If JumpVector = 0 And JumpHeight = 0 Then JumpVector = 5
                JumpHeight = Range(JumpHeight + JumpVector, 0, 255)
                JumpVector = JumpVector - 1
                If JumpHeight = 0 And JumpVector < 0 Then IsJumping = False: JumpVector = 0
            End If
            'Add new heart every 13 frames, but not with less than 2 seconds to go
            If (Level.LoveTimeout Mod 13) = 0 And (Level.LoveTimeout > 64) Then
                GameMedia.Particles.Create PT_HEART, Me.XPosition + 32, Me.YPosition, 32
            End If
        End If
        
        'If Mar has got the "Ghosts Eat Cookies" power up...
        If (Me.InLove = False) And (IsEaten = False) And (Level.PickupType = PT_GOOD_GHOSTSEAT) Then
            'Check if this ghost is over a cookie, and eat it
            Level.EatCookies Me
        End If
        
        'Check for collision with Pac Mar:
        'If the distance between her and this ghost is less than 24 pixels...
        If GetPythagDistanceFromPoint _
            (Level.Mar.PositionX + HalfTileSize, Level.Mar.PositionY + HalfTileSize, _
             Me.XPosition + HalfTileSize, Me.YPosition + HalfTileSize) < 24 _
        Then
            If IsInLove = True Then
                'Set the sprite into eaten mode if the ghost is in edible mode
                'The "Property Let Eaten" function will handle the changes for this
                Me.Eaten = True
            Else
                'Mar has been hit by a ghost, lose a life!
                Level.LoseLife
            End If
        End If
        
        'If the ghost is not moving (i.e. it's come up against a wall), choose a new
        'direction based on AI
        If (Me.IsMoving = False) Then Me.Direction = MakeDecision(Pos.X, Pos.Y)
    End If
End Sub

'====================================================================================
'ISPRITE MakeDecision: Provided a tile location on the level, decide where to move
'====================================================================================
Public Function MakeDecision(ByVal TileX As Long, ByVal TileY As Long) As PM_DIRECTION: MakeDecision = ISprite_MakeDecision(TileX, TileY): End Function
Private Function ISprite_MakeDecision(ByVal TileX As Long, ByVal TileY As Long) As PM_DIRECTION
    Dim NewDirection As PM_DIRECTION     'Proposed direction
    Dim Vec As PM_XY                     'A vector for the direction
    Dim Dest As PM_XY                    'Destination location
    
    Dim Tile As Byte
    Dim t As Long, n As Long, r As Long  'Temporary vars for the random directions
    Dim K As Long                        'Loop counter
    
    If IsEaten = True Then
        NewDirection = Level.GetDirectionToPen _
            (TileX, TileY, Level.Data.PenX, Level.Data.PenY, Me.Direction)
        
    Else
        'Apply AI depending on type of ghost
        Select Case AIType
            Case PM_GHOSTAI.AI_Hunt, PM_GHOSTAI.AI_Seek, PM_GHOSTAI.AI_Follow '______
                'Dib heads straight for Mar
                If AIType = AI_Hunt Then '-------------------------------------------
                    Dest.X = Level.Mar.PositionX
                    Dest.Y = Level.Mar.PositionY
                    
                'Zim trys to always be ahead of Mar
                ElseIf AIType = AI_Seek Then '---------------------------------------
                    'Create a vector for Mar's direction
                    Vec = GetVectorFromDirection(Level.Mar.Direction)
                    'Multiply up to three spaces ahead of Mar
                    Vec.X = (Vec.X * 3) * TileSize: Vec.Y = (Vec.Y * 3) * TileSize
                    
                    Dest.X = (Level.Mar.PositionX + Vec.X)
                    Dest.Y = (Level.Mar.PositionY + Vec.Y)
                
                'Kroc aims to always be behind Mar
                ElseIf AIType = AI_Follow Then '-------------------------------------
                    'Create a vector for Mar's direction
                    Vec = GetVectorFromDirection(Level.Mar.Direction)
                    'Reverse the vector
                    Vec.X = -Vec.X: Vec.Y = -Vec.Y
                    'Multiply up to three spaces behind Mar
                    Vec.X = (Vec.X * 3) * TileSize: Vec.Y = (Vec.Y * 3) * TileSize
                    
                    Dest.X = (Level.Mar.PositionX + Vec.X)
                    Dest.Y = (Level.Mar.PositionY + Vec.Y)
                End If
                
                'Decide the direction to go to reach the destination point
                NewDirection = p_MoveTowardPoint(TileX, TileY, Dest.X, Dest.Y)
                
            Case PM_GHOSTAI.AI_Random, PM_GHOSTAI.AI_Love '__________________________
                'Which directions are available to move based on walls? Returns 0 for
                'direction with no walls, 1 for directions with walls, also prevents
                'the ghost from backfacing
                Tile = p_RestrictDirections(TileX, TileY)
                
                'Choose a random direction at each junction, excluding the direction
                'you came from. Count the number of available directions
                t = Abs(IfNotBits(Tile, PM_WALL_TOP)) _
                  + Abs(IfNotBits(Tile, PM_WALL_RIGHT)) _
                  + Abs(IfNotBits(Tile, PM_WALL_BOTTOM)) _
                  + Abs(IfNotBits(Tile, PM_WALL_LEFT))
                'If there is only one direction to go...
                If t = 1 Then
                    '...go in the only available direction
                    NewDirection = (Not Tile And DIR_ALL)
                Else
                    'Choose a number between 0 and the number of directions available (-1)
                    Randomize
                    n = Int(Rnd(1) * t)
                    'Find the nth available direction
                    'Loop over the directions - up, right, down, left
                    r = 0: For K = 0 To 3
                        'If this direction is available...
                        '2^0=1(Up), 2^1=2(Right), 2^2=4(Down), 2^3=8(Left)
                        If (Tile And (2 ^ K)) = 0 Then
                            'If this is the one we want, choose that direction
                            If n = r Then NewDirection = (2 ^ K): Exit For
                            r = r + 1
                        End If
                    Next K
                End If
        End Select
    End If
    
    'A ghost cannot choose to go the direction they already are; that's automatic.
    If NewDirection = Me.Direction Then NewDirection = 0
    
    'Return AI Choice
    ISprite_MakeDecision = NewDirection
End Function

'====================================================================================
'PRIVATE MoveTowardPoint: Choose a direction to move to head toward a location
'====================================================================================
Private Function p_MoveTowardPoint(ByVal FromTileX As Long, ByVal FromTileY As Long, ByVal ToXpx As Long, ByVal ToYpx As Long) As PM_DIRECTION
    Dim K As Long
    'From the loction of the ghost on the level, calculate the direction to go in
    'order to reach a precise location on the level (in pixels)
    
    Dim Dist As PM_XY              'Distance X and Y - distance to destination
    Dim Sign As PM_XY              'Sign X and Y - positive or negative?
    Dim Tile As Byte                  'Direction availability
    
    'Which directions are available to move based on walls, Returns 0 for direction
    'with no walls, 1 for directions with walls, also prevents the ghost from
    'backfacing.
    Tile = p_RestrictDirections(FromTileX, FromTileY) And PM_WALL_ALL
    
    'Find the vertical and horizontal distances from target
    Dist.X = Abs(ToXpx - XPosition) 'Distance from ghost to destination
    Dist.Y = Abs(ToYpx - YPosition)
    Sign.X = Sgn(ToXpx - XPosition) 'Sign: 1 = positive, -1 = negative, 0 = 0
    Sign.Y = Sgn(ToYpx - YPosition)
    
    'First try to head towards the given point in the direction that the ghost is
    'furthest away from. If this is not possible, move in the first available
    'direction that is *towards* the given point
    If (Dist.X >= Dist.Y) And (Sign.X < 0) And IfNotBits(Tile, PM_WALL_LEFT) Then
        'If horizontal is the longest, go left if recommended/possible
        p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_LEFT)
    End If
    If (Dist.X >= Dist.Y) And (Sign.X > 0) And IfNotBits(Tile, PM_WALL_RIGHT) Then
        'If horizontal is the longest, go right if recommended/possible
        p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_RIGHT)
    End If
    If (Dist.Y >= Dist.X) And (Sign.Y < 0) And IfNotBits(Tile, PM_WALL_TOP) Then
        'If vertical is the longest, go up if recommended/possible
        p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_UP)
    End If
    If (Dist.Y >= Dist.X) And (Sign.Y > 0) And IfNotBits(Tile, PM_WALL_BOTTOM) Then
        'If vertical is the longest, go down if recommended/possible
        p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_DOWN)
    End If
    
    If p_MoveTowardPoint = 0 Then
        If (Sign.X < 0) And IfNotBits(Tile, PM_WALL_LEFT) Then
            'If left is towards Pac Mar, go left if possible
            p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_LEFT)
        End If
        If (Sign.X > 0) And IfNotBits(Tile, PM_WALL_RIGHT) Then
            'If right is towards Pac Mar, go right if possible
            p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_RIGHT)
        End If
        If (Sign.Y < 0) And IfNotBits(Tile, PM_WALL_TOP) Then
            'If up is towards Pac Mar, go up if possible
            p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_UP)
        End If
        If (Sign.Y > 0) And IfNotBits(Tile, PM_WALL_BOTTOM) Then
            'If down is towards Pac Mar, go down if possible
            p_MoveTowardPoint = AddBits(p_MoveTowardPoint, DIR_DOWN)
        End If
    End If

    'If no direction was available, then
    If p_MoveTowardPoint = 0 Then
        'If the ghost is on the same horizontal axis as Pac Mar...
        If Sign.X = 0 Then
            If IfNotBits(Tile, PM_WALL_LEFT) Then
                p_MoveTowardPoint = DIR_LEFT
            ElseIf IfNotBits(Tile, PM_WALL_RIGHT) Then
                p_MoveTowardPoint = DIR_RIGHT
            End If
        End If
        'If the ghost is on the same vertical axis as Pac Mar...
        If Sign.Y = 0 Then
            If IfNotBits(Tile, PM_WALL_TOP) Then
                p_MoveTowardPoint = DIR_UP
            ElseIf (Tile And DIR_DOWN) = 0 Then
                p_MoveTowardPoint = DIR_DOWN
            End If
        End If
    End If

    'If no desired direction has been set then choose the next available direction
    If p_MoveTowardPoint = 0 Then
        If IfNotBits(Tile, PM_WALL_TOP) Then
            p_MoveTowardPoint = DIR_UP
        ElseIf IfNotBits(Tile, PM_WALL_LEFT) Then
            p_MoveTowardPoint = DIR_LEFT
        ElseIf IfNotBits(Tile, PM_WALL_RIGHT) Then
            p_MoveTowardPoint = DIR_RIGHT
        ElseIf IfNotBits(Tile, PM_WALL_BOTTOM) Then
            p_MoveTowardPoint = DIR_DOWN
        End If
    End If
    
    'Choose a number between 0 and the number of directions available (-1)
    Dim t As Long, n As Long, r As Long
    t = Abs(IfBits(p_MoveTowardPoint, PM_WALL_TOP)) _
      + Abs(IfBits(p_MoveTowardPoint, PM_WALL_RIGHT)) _
      + Abs(IfBits(p_MoveTowardPoint, PM_WALL_BOTTOM)) _
      + Abs(IfBits(p_MoveTowardPoint, PM_WALL_LEFT))
      
    'If there is only one direction to go...
    If t > 1 Then
        'Choose a number between 0 and the number of directions available (-1)
        Randomize
        n = Int(Rnd(1) * t)
        'Find the nth available direction
        'Loop over the directions - up, right, down, left
        r = 0: For K = 0 To 3
            'If this direction is available...
            '2^0=1(Up), 2^1=2(Right), 2^2=4(Down), 2^3=8(Left)
            If (Tile And (2 ^ K)) = 0 Then
                'If this is the one we want, choose that direction
                If n = r Then p_MoveTowardPoint = (2 ^ K): Exit For
                r = r + 1
            End If
        Next K
    End If
End Function

'====================================================================================
'PRIVATE RestrictDirections: Before a decision is made, block off certain directions
'====================================================================================
Private Function p_RestrictDirections(ByVal TileX As Long, ByVal TileY As Long) As Byte
    'This function puts up invisible walls around certain directions to prevent the
    'ghost AI from heading certain directions. For example - ghosts cannot turn
    'around 180 degrees, nor can they enter the ghost pen unless eaten.
    Dim Tile As Byte
    Dim t As Long
    
    'Get the GetTile for the tile
    Tile = Level.Data.Tile(TileX, TileY)
    
    'The ghost cannot about-face
    Tile = AddBits(Tile, GetReverseDirection(Me.Direction))
    
    'Is the ghost within a one-square vicinity of the pen?
    If (IsEaten = False) Then
        t = Abs(IfNotBits(Tile, PM_WALL_TOP)) _
          + Abs(IfNotBits(Tile, PM_WALL_RIGHT)) _
          + Abs(IfNotBits(Tile, PM_WALL_BOTTOM)) _
          + Abs(IfNotBits(Tile, PM_WALL_LEFT))
        'If level with the pen
        If TileY = Level.Data.PenY Then
            'Check if aside the pen
            If TileX = (Level.Data.PenX - 1) Then
                'Left of the pen?
                If IfNotBits(Tile, PM_WALL_RIGHT) And (t > 1) Then Tile = AddBits(Tile, PM_WALL_RIGHT)
                
            ElseIf TileX = (Level.Data.PenX + 2) Then
                'Right of the pen?
                If IfNotBits(Tile, PM_WALL_LEFT) And (t > 1) Then Tile = AddBits(Tile, PM_WALL_LEFT)
            End If

        'If above the pen
        ElseIf TileY = (Level.Data.PenY - 1) Then
            If TileX = Level.Data.PenX Or TileX = Level.Data.PenX + 1 Then
                'Above the pen?
                If IfNotBits(Tile, PM_WALL_BOTTOM) And (t > 1) Then Tile = AddBits(Tile, PM_WALL_BOTTOM)
            End If

        'Below the pen
        ElseIf TileY = (Level.Data.PenY + 1) Then
            If TileX = Level.Data.PenX Or TileX = Level.Data.PenX + 1 Then
                'Above the pen?
                If IfNotBits(Tile, PM_WALL_TOP) And (t > 1) Then Tile = AddBits(Tile, PM_WALL_TOP)
            End If

        End If
    End If
    
    'Return the new set of available directions
    p_RestrictDirections = Tile
End Function

'====================================================================================
'ISPRITE Draw: Draw the ghost on the level, in the current direction and status
'====================================================================================
Public Sub Draw(ByVal hDC As Long): ISprite_Draw hDC: End Sub
Private Sub ISprite_Draw(ByVal hDC As Long)
    'Draw the ghost onto the level
    Sprite(CurrentAnim).Draw hDC, (XPosition - TileSize), (YPosition - Level.YScroll) - JumpHeight
End Sub

'====================================================================================
'CLASS Terminate: When the ghost is unloaded, free up memory
'====================================================================================
Private Sub Class_Terminate()
    'Release the sprite images loaded
    Erase Sprite
End Sub

'=== END ============================================================================
'This work is licenced under the Creative Commons Attribution 2.5 License
